Monitor Legislativo 2

A propósito del desarrollo de herramientas de análisis de datos con LLMs

codigo
apuntes
LLM
shiny
NLP
IA
Autor/a

Pedro Damián Orden

Fecha de publicación

27 de octubre de 2024

[ML II](https://dato.shinyapps.io/ai_parlamentaria)

[ML II](https://dato.shinyapps.io/ai_parlamentaria)

LLMs una nueva herramienta para la caja

Analizar grandes volúmenes de datos textuales, como los debates parlamentarios o registros abiertos, siempre ha sido un desafío complejo, y los métodos tradicionales de análisis— desde el conteo de palabras clave hasta el análisis general del tono o de similaridad— ofrecían solo una visión relativamente superficial. Las preguntas complejas, como identificar posturas políticas específicas o analizar los argumentos centrales de un legislador, solían quedar fuera de su alcance.

A la fecha, con los modelos de lenguaje de última generación, el panorama ha cambiado radicalmente. Estos modelos comprenden no solo el contenido, sino también el contexto y la estructura argumentativa de cada intervención. Esto, en definitiva, nos permite desplegar estrategias de análisis con una mayor complejidad, pues los modelos son capaces de inferir conexiones entre temas diversos y de identificar relaciones contextuales y estructurales que antes requerían de análisis humano detallado. Al captar matices, premisas y la evolución de ideas a lo largo de un texto, estos modelos logran una comprensión transversal que relaciona conceptos de distintas áreas, permitiendo un enfoque de análisis profundo que considera tanto los argumentos explícitos como los implícitos. Esta capacidad de relacionar ideas y detectar patrones de razonamiento dota a los modelos de una suerte de “inteligencia contextual” que va más allá de la interpretación literal, revelando no solo la lógica interna del discurso, sino también posibles contradicciones o líneas argumentativas que enriquecen el análisis.

Con la experiencia de desarrollo que repasaremos, veremos cómo, mediante un enfoque de código abierto, podremos estructurar el Monitor Legislativo 2 para articular el poder de los LLMs en el análisis y desglose de debates parlamentarios. Este enfoque tiene el potencial de optimizar el proceso de interpretación, ofreciendo claridad y precisión en el análisis de cada sesión, incluyendo aquellas de gran interés público, como la reciente en la que se rechazó el veto al aumento universitario.

A lo largo de este recorrido, exploraremos cómo el monitor puede descomponer los discursos en sus componentes esenciales, permitiendo identificar tanto las posturas como los contapuntos y argumentos que definen las posiciones de los diputados y diputadas nacionales en un tema tan relevante como el financiamiento público a la educación universitaria. La potencia de la consulta semántica y la exploración contextual que buscamos implementar facilitará un análisis activo, donde cada intervención podrá ser interpretada en función de su contexto y relevancia, acercando al usuario a una comprensión compleja de las dinámicas políticas en juego.

Shiny y por qué volver usarla para el Monitor Legislativo 2

Shiny es un framework de R que nos permite crear aplicaciones web interactivas sin necesidad de conocimientos avanzados en desarrollo web. Con Shiny, es posible construir aplicaciones potentes y dinámicas que pueden visualizar y procesar datos en tiempo real. La combinación de su simplicidad y capacidad para acoplarse con bibliotecas de R hace que sea una opción ideal para proyectos de análisis de datos y visualización interactiva, como el Monitor Legislativo 2.

El funcionamiento general de Shiny se basa en dos componentes principales:

  • La interfaz de usuario (UI), donde se diseña la apariencia y organización de la app. En este caso, organizamos la UI en tarjetas para filtrar los discursos, mostrar el contenido del discurso y los resultados del análisis.

  • El servidor, que es el núcleo donde ocurre el procesamiento de datos. Aquí es donde Shiny ejecuta el código en el backend para realizar tareas complejas, como conectar la app con el modelo de lenguaje Gemini y analizar los discursos parlamentarios en tiempo real.

Una de las grandes ventajas de Shiny es su naturaleza reactiva, que permite que la aplicación responda instantáneamente a las interacciones del usuario, actualizando gráficos, textos y análisis de forma dinámica sin recargar la página.

Además, incorporaremos el uso de bslib, una herramienta que nos permitirá mejorar significativamente la experiencia visual y de navegación dentro de la interfaz. Con bslib, conseguiremos personalizar y optimizar la UI para dar con una usabilidad más intuitiva y atractiva, elevando la experiencia de usuario.

En caso de querer conocer más sobre shiny recomiendo este post donde explico más sobre el tema y abro código.

Configuraciones iniciales

Antes de definir la interfaz y el servidor de Monitor Legislativo 2, es esencial realizar algunas configuraciones iniciales que permitirán el correcto funcionamiento de la aplicación. Este seteo, incluyen la carga de librerías, datos, la definición de variables de entorno y funciones clave que se utilizarán en el análisis de los discursos.

Revisemos cada paso a continuación.

Importante: sobre el código completo

El código completo de la app se encuentra abierto en github.

Mi primera recomendación en caso de que quiera replicar la experiencia es que clonen el repositorio y lo ejecuten luego de leer el documento. Armé una estructura simple donde todo el código del monitor corre en un sólo script (app), lo cual puede orientar mejor a los que recién empiezan con Shiny.

La segunda recomendación es que si van a iterar el código modularicen las funciones.

Entendamos ahora qué hace cada parte.

Carga de Librerías Necesarias

En primer lugar, se cargan las librerías que la aplicación utilizará, abarcando desde la interfaz de usuario hasta el análisis de texto. Estas incluyen shiny y bslib para la estructura y tematización de la app, highcharter para visualizaciones avanzadas, gemini.R para interactuar con el modelo de lenguaje, y otras librerías para el manejo de datos, entornos, y gráficos.

# Cargar librerías necesarias para construir y personalizar la app
library(shiny)         # Framework para construir aplicaciones web interactivas en R
library(bslib)         # Para temas y estilos Bootstrap en Shiny
library(dplyr)         # Manipulación de datos
library(highcharter)   # Gráficos interactivos
library(gemini.R)      # Integración con el modelo Gemini
library(htmltools)     # Funciones de ayuda para manipular HTML
library(markdown)      # Procesamiento de texto en formato Markdown
library(tm)            # Procesamiento de texto (Text Mining)
library(dotenv)        # Manejo de variables de entorno
library(bsicons)       # Iconos de Bootstrap
library(thematic)      # Temas de estilo de gráficos
library(vembedr)       # Integración de videos en la app

Configuración de Variables de Entorno

La aplicación utiliza dotenv para gestionar variables sensibles, como la clave API de Gemini, que se almacena en un archivo .env. Este paso es clave para mantener la seguridad de la clave y facilitar el acceso a los recursos del modelo de lenguaje. La clave se carga desde el archivo .env y se verifica su existencia antes de proceder.

dotenv::load_dot_env()

# Establecer la clave API de gemini usando la variable de entorno
api_key <- Sys.getenv("GEMINI_API_KEY")

if (is.null(api_key) || api_key == "") {
  stop("No se encontró la clave API. Asegúrate de definir GEMINI_API_KEY en el archivo .env.")
}

setAPI(api_key)

Es de esta manera que garantizamos que la clave API esté disponible y correctamente configurada antes de hacer cualquier solicitud al modelo. Si la clave no se encuentra, la aplicación genera un mensaje de error que previene una ejecución fallida.

Carga y Validación del Archivo de Datos

Nuestros datos proviene de una extracción previa vía la api de youtube al canal de Diputados de la Nación. Veamos como lucen:

sesion_universidad <-read.csv("Sesión Especial 09-10-2024 .csv")

head(sesion_universidad)
  X    video_id                  diputado fuerza_politica
1 1 -PbC_5En8gU Iglesias, Fernando Adolfo             PRO
2 2 0hlAJBKAW8Q         Espert, José Luis             LLA
3 3 2B6pLQ873jo        Osuna, Blanca Inés             UxP
4 4 7zQ7Hyxq7qk           Massot, Nicolás              EF
5 5 9WbOaj2o2Tc            Picón Martínez             PyT
6 6 A-eweSyIoH0    Finocchiaro, Alejandro             PRO
                             link
1 youtube.com/watch?v=-PbC_5En8gU
2 youtube.com/watch?v=0hlAJBKAW8Q
3 youtube.com/watch?v=2B6pLQ873jo
4 youtube.com/watch?v=7zQ7Hyxq7qk
5 youtube.com/watch?v=9WbOaj2o2Tc
6 youtube.com/watch?v=A-eweSyIoH0
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         text
1 tiene la palabra a continuación el diputado Fernando Iglesias por el bloque Pro Gracias presidente un diputado amigo se preguntaba qué estamos discutiendo y qué no estamos discutiendo No estamos discutiendo el valor ni la continuidad de la universidad pública no hay ningún plan de desmantelamiento mienten mienten Sí señor diputado mienten los que dicen eso y mienten sin pruebas lo que estamos discutiendo es la plata el dinero los recursos la caja la guita los morlacos la vuya nos acordamos la educación cuando tocan las cajas y la vuya y lo hacemos en defensa de la educación pública Mire yo vi destruir la educación pública Argentina el drama de la educación pública Argentina no está en la universidad está en la primaria y está en la secundaria discutimos eso contemos las horas que llevamos discutiendo el presupuesto universitario contra las horas que venimos discutiendo la educación pública primaria y secundaria donde se educan los más vulnerables mentira que estamos discutiendo la educación pública estamos discutiendo la guita la vuya saben Cuántos son los argentinos pobres que se que se educan en las universidades públicas el 10% son el 40% en las escuelas primarias el 36 por en las escuelas secundarias discutimos el presupuesto de la educación primaria y secundaria no el de la 10% 10% de los estudiantes vien de la universidad vienen de hogares pobres y Con qué se financian con un esquema con un esquema tributario Injusto y regresivo que nos dejaron los campeones de la justicia social después de cuatro gobiernos en donde casi todo se paga con la guita de las empresas productivas y con el IVA de la polenta hipócritas no mentiros mentirosos e hipócritas digamos la verdad los que egresan de las universidades públicas argentinas son en su mayaría graduado se graduaron en la secundaria privada pagando aranceles y aranceles altos no ponen un peso en aranceles para la educación pública 10% son pobres mucho menos de la mitad son los que se reciben a esos es lo que hay que financiar si de verdad son progresistas eso es lo que no se discute el modelo fracasado de esta universidad pública la mitad Cuando hacemos la cuenta de la cantidad de argentinos que tienen diplomas universitarios es la mitad que en chile y la mitad que en Brasil y los acusamos de neoliberales hipócritas la mitad Con el IVA de la polenta y sacamos la mitad y los que los que entran los que se gradúan en Argentina el éxito universitario es la mitad que en Brasil y que en chile y seguimos mintiendo porque defendemos este modelo fracasado en nombre de lo que debe ser En nombre del ascenso social cuál ascenso social me causa gracia la señora crisa Fernández de diciendo que viene de la universidad pública y le fue bien Claro claro a mí también normal de avellaneda Instituto Nacional de Educación Física Instituto Nacional de deportes Universidad de Lomas de sabora universidad de boloña todas estatales y con eso qué rompieron eso a mi generación cuando Yo estudié había ascenso social lo rompieron y lo rompió el peronismo con la complicidad de los que no fueron oposición cuando debieron serlo Así que mentira lo del ascenso social Debería ser pero no es era pero no es no defiendan Este modelo fracasado en nombre de lo que debería ser ni de lo que fue universidad al Servicio del pueblo me dicen Qué servicio del pueblo tres abogados y contadores por cada ingeniero al revés que en Chile los neoliberales caraduras acá lo único que se discute es el equilibrio fiscal y lo quieren romper lo quiere romper el el club del helicóptero porque sabe que es el núcleo del plan de este gobierno y la única forma que tenemos de tener una universidad pública con buenos salarios es tener un país que crezca y eso es lo que rompieron durante 20 años rompieron el equilibrio fiscal y así no fue por eso todos los países de latinoamérica mejoraron bajaron la inflación mejoraron el pbi Per capita mejoraron la salud y la educación Todos Menos nosotros y Venezuela Y eso es lo que está defendiendo acá el peronismo y los que sufren el síndrome de estocolmo saben y lo estamos discutiendo un país que tienen termino estamos discutiendo en un país que tiene más del 50% de sus habitantes en la pobreza saben Cuándo fue la primera vez que pasó en la Argentina Acá está diario de una temporada en el quinto piso se lo recomendé al diputado Loredo que parece que no lo leyó dice 0,14 por un poquito de acá 0,40 por. Sabes lo que dice este libro mi única coincidencia con Cristina Fernández CR que ella recomendó que leeran este libro lean este libro es un miembro del equipo económico de Sur ruil se acuerdan del plan austral arrancó con una inflación mensual del 25% y en un mes dos meses estaba en el 3% le suena 25% 3% le suena sa saben cómo lo rompieron con excelentes razones excelentes razones iban alfonsin y decían presidente la franja morada los profesores universitarios 014 bueno presidente los salarios de los estatales c coma tanto por c presidente los militares si no nos van a dar el golpe c coma no sé cuá por cento y cero de acá y cero sabe cómo terminó esto terminó en la hiperinflación así terminó inflación terminaron los profesores universitarios los profesores los jubilados los asalariados y fue la primera vez que la Argentina que la Argentina Tuvo más de 50% de la población en la pobreza le radicales correligionarios lean de su propia experiencia y me parece muy bien Que sigan reivindicando el legado político del doctor alfonsín es hora también de que hagan una autocrítica de sus políticas económicas gracias señora presidenta s
2                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   [Aplausos] tiene la palabra a continuación el diputado eser previamente les comunico que es el penúltimo orador después el diputado esper hablar la diputada Juliana Santillán y procederemos a votar en el día de la fecha Así que vayan acercándose Quienes se encuentran en el bajo recinto Muchas gracias adelante diputado esper Gracias señor presidente la educación Universitaria pública no está en peligro no tengan miedo Es ridículo pensar que un gobierno que estuvo detrás de la sanción de la educación básica como servicio estratégico esencial el gobierno el presidente Javier Miley está detrás de la destrucción de la educación Universitaria pública falso de toda falsedad lo que sí queremos es cada vez mejores profesores cada vez mejores graduados universitarios cada vez mejores investigaciones eso es lo que queremos lo que no queremos son curros que se hagan detrás de las universidades con el dinero de todos los contribuyentes muchos de ellos pobres que no estudian o no van a poder estudiar Lamentablemente y en eso sí los legisladores deberíamos estar preocupados el gobierno el presidente Miley este año va a gastar en la educación Universitaria 3.5 billon casi 3000 millones de d y eso destruir la educación pública no sean ridículos no sean ridículos eso es más del 100% con respecto a lo que se gastó el año pasado en materia Universitaria se ha aumentado los gastos de funcionamiento 270 por está muy lejos eso de significar la destrucción de la educación Universitaria todo lo contrario educación Universitaria queremos Educación Pública pero cada vez con mayor calidad y menos curro la educación Universitaria no está en peligro pero si tenemos algunos problemas nombro algunos de ellos heredamos un sistema educativo básico destruido donde la mitad de Los chicos secundari no lo terminan % de los estudiantes universitarios tardan casi 10 años en terminar carreras que se podrían terminar en cinco solo cuatro de 10 estudiantes son regulares O sea que han aprobado dos materias o más una vergüenza solo el 12,4 de los jóvenes más pobres van a la universidad y menos de la mitad menos de la mitad termina la universidad Dónde está la universidad pública inclusiva algo tenemos que hacer con esto no será que algunos vivos Han inventado el curro de financiar con los pobres la universidad que se podrían pagar no seamos hipócritas Aunque Argentina tiene mayor cantidad de estudiantes universitarios que Chile la tasa de graduación de nuestra universidad es muchísimo más baja 40% más baja con mayor cantidad de estudiantes universitarios tenemos estudiantes universitarios crónicos no nos engañemos hipócritas con estos datos no hay duda que algo tenemos que hacer con la universidad ahora no nos engañemos como ya dijo un diputado preopinante este proyecto de ley no significa ningún cambio para la universidad sino significa discutir más presupuesto o menos presupuesto y yo le digo a los que han impulsado la sanción de este proyecto de ley es el presupuesto donde se discute el gasto Público de cada año no fuera el presupuesto con un proyecto amañado que encima indexa el gasto universitario el gasto público tiene que estar completamente tenga sentido el presupuesto si no dejemos de sancionar el presupuesto si todo el gasto va a estar indexado qué sentido tiene el presupuesto voy a un tema central que es lo que muchos reclamamos de acuerdo con la propia ley de educación superior las universidades además de ser financiadas por el gobierno nacional tienen que ser auditadas por el gobierno nacional a través del agn y a través de la cgen si de la el control interno del poder ejecutivo y debo decirles para aquellos que no se han dado cuenta que por ejemplo el informe más reciente que es una auditoría de gestión de la Universidad de Buenos Aires es del año 2006 y Es sobre el año 2004 hace 20 años que la universidad de Buenos Aires le debe a todos los argentinos una rendición de cuentas en particular aquellos argentinos que no pueden estudiar Porque son pobres este gobierno cumplió el el sueño de muchos argentinos que fue promover la educación como servicio esencial repito estamos muy lejos de querer terminar con la educación pública de hecho les voy a decir que yo soy egresado de grado de la universidad pública cuando graduarse en la universidad pública Universidad de Buenos Aires era un orgullo y no el kiosco en el cual se ha convertido hoy y eso es lo que queremos terminar por eso vetar esta ley es la obligación de un buen argentino Muchas gracias señor presidente Gracias diputado José Luis
3                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      [Aplausos] palabra Gracias presidente atrevido diputado por fa respetuoso por favor Le pido diputado Iglesias a ver creo presidente me des cuenta estas molestias por favor pero creo sí no le he puesto el tiempo diputada por favor diputado por favor ponga orden presidente diputado igles por favor adelante diputada tiene la palabra la verdad que la molestia la molestia que provocan las palabras del presidente de mi bloque en realidad tiene que ver con que señaló puso el dedo en algo que es álgido lo que Hoy estamos discutiendo acá la verdad que puede tener un resultado disímil Sí sí o no en ambos casos la el resultado va a tener legalidad en en cualquiera de los casos el impacto el impacto y los resultados van a ser absolutamente contrapuestos Y eso es importante que queden Claro porque en realidad presidente y colegas legisladores legisladoras acá se trata de entender que no podemos analizar debatir y votar en esta ley si no tenemos una mirada de su texto y del contexto que acá ha quedado claramente en evidencia acá hemos escuchado legisladores legisladoras que dicen que este no es un ámbito para discutir sal acá hemos escuchado legisladores legisladoras que plantean que acá se resolvió un tema salarial Mentira mentira la discusión salarial de los docentes y en particular de los docentes y no docentes de las universidades públicas argentinas se discute y se resuelve en las paritarias que tienen una ley respaldatoria pero más allá de eso es importante tener en claro que el gobierno recortó el 50% del gasto en educación Durante este año y que el gasto de la Secretaría de Educación se redujo en un 56 por de modo de qué hablan que las cosas están mejor gravemente están empeorando de manera acelerada en los primeros meses en los nu primeros meses de este año ao la caída comparada entre el año pasado y este y este año es de 48,6 por el programa desarrollo de la educación superior que es el programa más relevante que representa un 77 por del gasto global que tiene la Secretaría de Educación Tuvo una de casi el 50% el 99 de ese 99% de ese programa se compone por transferencias a las universidades y ahí están los programas de fortalecimiento de las actividades de extensión del fondo universitario para el desarrollo Regional del fortalecimiento de la ciencia y la técnica que trae consecuencias gravísimas para el desarrollo nacional los proyectos especiales el el financiamiento de los institutos tecnológicos todo eso está al borde de desaparecer si efectivamente no hay una respuesta hay hay muy hay datos que que en estos momentos estamos manejando porque se presentó el presupuesto nacional y ahí ya hay indicadores de que esta disminución presupuestaria va agravándose va in crecendo Y por supuesto también ahí ponemos en una mirada integral sobre lo que es el sistema educativo donde las universidades son una parte trascendente importante pero ahí están el fonit la infraestructura Y equipamiento de qué Jardines de infantes hablan si no han puesto un peso en esos jardines en innovación y desarrollo en fortalecimiento en conectar igualdad hay jóvenes hay niños niñas de nuestro país que hoy están privados de educarse mejor porque no cuentan con libros porque no van a tener el equipamiento correspondiente en las escuelas porque no va a haber un aula más entonces la verdad presidente yo creo acá han sucedido cosas llamativas eh de los 256 que somos verdad 57 Perdón 257 257 Acá hay un exministro entre nosotros ex ministro de Educación ex ministro de Educación docente universitario que no habló que no habló es llamativo es llamativo Por supuesto es muy llamativo mire presidente la verdad que el feroz ataque que está padeciendo la universidad pública nos avergüenza y creo Creo que de las 4900 carreras de grado que de los más de 2 millones y medio de estudiantes que se han acercado a nosotros que han planteado que nos han acercado notas yo voy a elegir algo y voy finalmente algo todas not acerca el movimiento de universitarios del con urbano de universidades es una condición indispensable para el futuro del país esta nota está dirigida a todos y todos la van a recibir si no la han recibido ya sin él no habrá desarrollo Innovación ni soberanía exido no haber defendido nuestras universidades por eso porque creemos en un desarrollo integral de nuestro país las universidades son condición necesaria hoy Esto presidente y lo último que digo Hoy estamos frente está faltando a la punta de un iceber hoy no se termina nada hoy no se termina nada y todos lo sabemos acá Gracias presidente Gracias diputada le pido por
4                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              reglamento mire presidente eh No estoy tranquilo estoy esperando que escuche porque me voy a dirigir a él presidente no es la primera vez en este año en su presidencia que por cuestiones de la dinámica de la dinámica [Música] que por cuestiones de la dinámica de esta cámara y de los debates haya alguna alteración al plan de labor tampoco va a ser la primera vez que para respetar el plan de labor decidamos votar mociones de orden y si resultan afirmativas las incorporaciones tratarlas al final tal vez esa pueda ser una solución presidente Usted lo ha hecho en sesiones en el pasado en su propia presidencia si quiere le escrib un mensaje de texto si lo va a leer desde el teléfono no tengo problema Estoy escuchando diputado puede hablar no parece no parece y va a tener que estoy escuchando diputado estoy cumpliendo con lo que nos hemos comprometido todos en la voz parlamentaria contin por favor Entonces no me está escuchando no me está escuchando Pues nosotros las mociones no atentan contra los planes de labor y el plan de labor se puede cumplir simplemente respetando las mociones que tenemos para hacer ahora votá y se ultan afirmativas y es una incorporación al temario por ejemplo que puede ser entonces las votamos al final lo que le pido presidente no está fácil el clima acá se viene una discusión extremadamente relevante para toda la nación Argentina que es una discusión que su propio gobierno nos privó de tener este año que es un presupuesto nos va a llevar mucho trabajo a hacerlo no es tiremos no tratemos de reescribir Desde esa banca que es de un primus interpares y usted no es el jefe de nadie un reglamento que está claro Entonces por favor tenga la prudencia de respetar las mociones hacerlas votar y en todo caso por supuesto respetemos el plan de labor y al final de las cuestiones de privilegio y de los homenajes puedan tratarse en el caso de resultar afirmativas le pido por favor que lo considere señor secretario puede leer por
5                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   tiene la palabra a continuación la diputada por el bloque producción y trabajo San Juan Nancy picón Gracias señor presidente Bueno desde el bloque producción y trabajo creemos en que la educación es esencial para el desarrollo de una nación es por esto que en primer lugar repudiamos absolutamente la presencia en la marcha de quienes destruyeron la educación la presencia en la marcha de masa de Cristina Fernández de kisner haciéndose hoy dueños de una de un reclamo justo de un sector nada más y nada menos que el sector que es el futuro de nuestro país de los jóvenes es por esto que sabemos que hay que hacer un ajuste que este ajuste es necesario que es el ajuste que este presidente les dijo a cada uno de los argentinos en campaña Y que además votaron pero el ajuste no podemos hacerlo con la educación podemos hacerlo desde cualquier otro sector pero no con la educación que es el futuro del país no podemos haber votado Hace unos días la esencialidad en la educación y hoy dejar sin fondos a esa esencialidad no digo Tenemos que ser coherente con lo que venimos votando este bloque producción y trabajo desde San Juan siempre va a defender la universidad por varias razones y en mi caso particular porque soy alumna soy hija de la Universidad Nacional de San Juan con mucho orgullo fui docente en la misma universidad y yo sí puedo decir soy la madre del doctor quien Se recibió en la Universidad Nacional de Córdoba con todo lo que la universidad nos ha dado no solo a nivel familiar sino también en la provincia y nos sigue dando convencidos de que tenemos muchos lugares donde seguir trabajando para que existan los fondos muchos muchas partidas y que Próximamente estamos ahora hemos empezado a trabajar el presupuesto y este presupuesto empezará a regir en el 2025 si Dios quiere y se habrán los nuevos fondos para las universidades desde ese lugar hoy señor presidente el voto de San Juan va a ser a favor de de las universidades y de la educación pública Muchas gracias Gracias diputada picón
6                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 finaro tiene la palabra lo han aludido Muchísimas gracias señor presidente voy a aprovechar la alusión que hizo el diputado Valdés para dar una breve explicación Este primer lugar Usted sabe que desde el mismo momento que asumí como diputado nacional hay una diputada en este recinto que tiene una fijación Conmigo quiere constantemente digamos es mi Pos decir por no he hablado no he hablado no he hablado porque me sentí yo los escucho a todos silencio por favor nunca interrumpo a nadie les pido por favor que hagan lo mismo yo no he hablado porque me siento parte de un bloque orgánicamente y quienes hablaron me representan porque no importa quien hable en un bloque sino la posición que se sostenga En tercer lugar no ha porque no necesito de la estridencia de este micrófono para hablar porque junto con el presidente de mi bloque y la secretaria parlamentaria y otros diputados hablando con el gobierno conseguimos que se incluya la garantía salarial para los docentes que menos ganan y para los no docentes Y eso es plata y eso es trabajar fuera de lacia beneficiar al otro Eso es trabajar para beneficiar al otro En tercer lugar sabe qué señor presidente acá no estamos discutiendo como ya se dijo un modelo de universidad acá estamos discutiendo una paritaria la paritaria no se cierra en el congreso porque falta que venga mollano a decirnos que no cierra la paritaria camionero y nosotros tengamos que ponerle el precio Pero además Y por último señor presidente Para no alargar más esta intervención vamos a ser sinceros no seamos hipócritas acá no estamos hablando de las universidades hace 27 años que educo en las universidades he educado a miles de alumnos sin adoctrinarlos y dándole la oportunidad de tener criterio propio acá estamos hablando con el bloque de Unión por la patria traducido el kissnerismo y no hablo de los otros bloques de una disputa de poder acá estamos hablando de el mensaje que este recinto va a mandar a los mercados internacionales a los inversores que tanto necesitamos y a las consultoras de riesgo de eso estamos hablando y le voy a decir una cosa señor presidente aún con los matices que yo tengo con este gobierno si en una disputa de poder yo tengo que elegir silencio por favor ha sido aludido votar con el populismo que ha dejado el efecto devastador de una guerra en la República Argentina o un gobierno que me habla de prosperidad de libertad y de hacer grande Argentina de vuelta señor presidente yo me corto la mano antes de votar con el quisis Muchas gracias gracias diputado finaro

Nos encontramos con los discursos de diferentes diputados en el contexto de una sesión parlamentaria reciente, centrada en el debate sobre el presupuesto universitario. Los campos incluyen:

  • ID del video (video_id): Identificador único de YouTube que permite acceder a los videos de cada discurso.

  • Diputado (diputado): Nombre del diputado o diputada que realizó el discurso.

  • Fuerza política (fuerza_politica): Agrupación política a la que pertenece el orador, como PRO, LLA (La Libertad Avanza), UxP (Unión por la Patria), entre otras.

  • Enlace (link): URL completa del video en YouTube, facilitando el acceso directo a la intervención específica.

  • Texto del discurso (text): Transcripción completa del discurso pronunciado, lo cual permite analizar los argumentos, posturas y palabras clave de cada intervención parlamentaria.

En nuestra app, el archivo CSV con los discursos parlamentarios se carga y valida para asegurarnos de que contiene las columnas necesarias. Esto es importante para evitar errores en la ejecución y garantizar que la estructura de datos es la esperada.

file_path <- "Sesión Especial 09-10-2024 .csv"
if (file.exists(file_path)) {
  discursos_df <- read.csv(file_path, stringsAsFactors = FALSE)
} else {
  stop("El archivo no se encuentra en el directorio de trabajo.")
}

required_columns <- c("fuerza_politica", "diputado", "text", "video_id")
if (!all(required_columns %in% colnames(discursos_df))) {
  stop("El DataFrame 'discursos_df' no contiene las columnas necesarias: fuerza_politica, diputado, text, video_id.")
}

En caso de que falte el archivo o alguna columna, el código detiene la ejecución y muestra un mensaje de error claro, ayudando a depurar el problema antes de que avance.

Función para generar la nube de palabras

La función generar_nube_palabras procesa el texto del discurso seleccionado y genera una nube de palabras, resaltando los términos más frecuentes. Este análisis visual agrega valor al momento de identificar rápidamente temas y palabras clave sin tener que leer el discurso completo. La función sigue estos pasos:

  1. Eliminación de stopwords: Primero, el texto del discurso se convierte a minúsculas y se divide en palabras individuales. Luego, se eliminan las palabras comunes o “stopwords” en español, como “de”, “la”, “que”, entre otras, para dejar únicamente los términos significativos. Esto se logra definiendo una lista personalizada de stopwords en español.

  2. Frecuencia de palabras: Tras eliminar las stopwords, la función crea una tabla de frecuencia con las palabras restantes, donde cada palabra significativa se cuenta. Para que los términos resultantes sean informativos, se filtran las palabras de tres letras o menos, así como aquellas que aparecen solo una vez.

  3. Generación de la nube de palabras: Si la tabla de frecuencia tiene datos suficientes, se utiliza highcharter para crear una visualización de nube de palabras. La visualización muestra cada término con un tamaño proporcional a su frecuencia en el texto. Si no hay suficientes palabras para generar una nube, se muestra un gráfico alternativo con un mensaje informativo.

generar_nube_palabras <- function(texto) {
  stopwords_es <- c(stopwords("es"),"aplausos", "que", "para", "como", "más", "muy", "pero", "también", "por", "entre", "sobre", "nos", "las", "los", "una", "a", "de", "del", "y", "e", "con", "el", "la", "su")
  
  palabras <- strsplit(tolower(texto), "\\W+")[[1]]
  palabras <- palabras[palabras != ""]
  palabras <- palabras[!palabras %in% stopwords_es]
  
  freq_palabras <- as.data.frame(table(palabras))
  colnames(freq_palabras) <- c("word", "n")
  freq_palabras <- freq_palabras[nchar(as.character(freq_palabras$word)) >= 3, ]
  freq_palabras <- freq_palabras[freq_palabras$n > 1, ]
  
  if (nrow(freq_palabras) == 0) {
    return(highchart() %>%
             hc_title(text = "Nube de Palabras - Sin datos suficientes") %>%
             hc_subtitle(text = "No hay suficientes palabras para generar la nube") %>%
             hc_chart(backgroundColor = "#f7f9fc"))
  }
  
  hchart(freq_palabras, "wordcloud", hcaes(name = word, weight = n)) %>%
    hc_title(text = "Nube de Palabras del Discurso") %>%
    hc_tooltip(pointFormat = "<b>{point.name}</b>: {point.weight}") %>%
    hc_chart(backgroundColor = "#f7f9fc")
}

Función para obtener la respuesta del modelo de lenguaje

La segunda función, obtener_respuesta_sobre_discurso, utiliza el modelo de lenguaje Gemini para responder preguntas específicas sobre el relato del protagonista. Esta función envía un mensaje al modelo con el texto completo del discurso y una consulta en lenguaje natural que el usuario ha seleccionado o ingresado. La estructura del prompt es clave para obtener respuestas precisas y contextuales. Aquí, detallamos su funcionamiento:

  1. Estructuración del prompt: El prompt combina el discurso con una pregunta en un formato claro para el modelo. El prompt incluye:

    • Contexto: Explica al modelo que está analizando un “discurso parlamentario” en el contexto de la cámara legislativa argentina.

    • Información adicional: Le aclara al modelo que se espera una respuesta detallada, precisa y “basada en el discurso proporcionado”, limitando el alcance de la respuesta.

    • Consulta específica: El usuario puede ingresar una pregunta personalizada o seleccionar una predefinida, que se incorpora en el prompt como tarea a resolver.

  2. Manejo de errores: La función emplea tryCatch para capturar cualquier error que pueda ocurrir durante la comunicación con el modelo. Si se produce un error, la función devuelve un mensaje informativo en lugar de la respuesta del modelo, indicando al usuario que intente con otra consulta o selección.

obtener_respuesta_sobre_discurso <- function(discurso, pregunta) {
  prompt <- paste(
    "Eres un experto en discursos políticos. A continuación te proporciono el discurso completo en el parlamento argentino del diputado/a:",
    "\n\n", discurso, 
    "\n\nTu tarea es la siguiente: ", pregunta, 
    "\nPor favor, brinda una respuesta clara basada en el discurso proporcionado."
  )
  
  tryCatch({
    respuesta <- gemini(prompt)
    return(respuesta)
  }, error = function(e) {
    return("Ups! no tengo datos para esa selección, por favor probá con otra.")
  })
}

Como en todo proyecto que involucra LLMs, el prompt es el corazón de esta función, ya que define cómo el modelo interpreta el texto y genera la respuesta. Al incluir un contexto detallado y una tarea específica, nuestro prompt asegura que el modelo se sitúe en clave política y se enfoque solo en el discurso dado.

Diseñando la Interfaz

Para armar la interfaz del ML II, vamos a apoyarnos en bslib y Bootstrap 5. La meta aquí es contar con algo que no solo sea funcional, sino también intuitivo y fácil de usar. Con esto en mente, organizaremos la app en tres tarjetas: una para seleccionar los inputs, otra para mostrar el contenido del discurso y una tercera para presentar los resultados de análisis generados por el modelo de lenguaje.

Primer panel: selección de parámetros para el análisis

La primera tarjeta, dedicada a la selección de parámetros, permite al usuario configurar rápidamente los filtros y criterios para el análisis del discurso. Este panel incluye un selectInput que despliega los partidos políticos disponibles, basado en los datos de discursos_df, asegurando que solo se muestren partidos válidos. Además, utiliza uiOutput para actualizar dinámicamente la lista de candidatos, mostrando únicamente aquellos que pertenecen al partido seleccionado. La tarjeta también presenta un selector de análisis con radioButtons, que permite al usuario elegir entre un análisis predefinido o ingresar una consulta personalizada. Los conditionalPanel actúan como un filtro adicional, revelando solo las opciones de análisis relevantes según la selección. Finalmente, dos botones de acción, “Analizar Discurso” y “Limpiar Pregunta”, ejecutan el análisis del discurso o reinician la consulta, dando control total al usuario sobre la ejecución del análisis.

# Tarjeta de Selección de Parámetros para el Análisis
fluidRow(
  column(width = 3,
    card(
      card_header("Seleccione los parámetros", icon("filter")),
      card_body(
        selectInput("partido", "Seleccione el Partido:", choices = unique(discursos_df$fuerza_politica)),
        uiOutput("candidato_ui"),
        radioButtons("pregunta_tipo", "Línea de análisis:", choices = list("Predefinida" = "predefinida", "Personalizada" = "personalizada")),
        conditionalPanel(
          condition = "input.pregunta_tipo == 'predefinida'",
          selectInput("pregunta_predefinida", "Seleccione una línea de análisis predefinida:",
                      choices = list(
                        "Identificar postura" = "Identificar postura",
                        "Resumen" = "Resumen",
                        "Principales argumentos" = "Principales argumentos"
                      ))
        ),
        conditionalPanel(
          condition = "input.pregunta_tipo == 'personalizada'",
          textAreaInput("pregunta_personalizada", "Escribí tu propia pregunta:", "", height = "100px")
        ),
        actionButton("analizar", "Analizar Discurso", class = "btn btn-primary btn-lg btn-block mt-3"),
        actionButton("limpiar", "Limpiar Pregunta", class = "btn btn-secondary btn-lg btn-block mt-2")
      )
    )
  ),
)

Segundo panel: visualización combinada video-nube

La siguiente tarjeta se centra en la visualización del contenido del discurso. Este panel combina dos vistas en una estructura de pestañas, utilizando tabPanel dentro de navset_card_tab. La primera pestaña muestra el video del discurso con uiOutput("video_embed"), una función que permite incrustar un video de manera responsiva, manteniendo su visualización clara incluso en dispositivos móviles. Si el video no está disponible, se muestra un mensaje de error, garantizando una comunicación clara con el usuario. La segunda, contiene una nube de palabras generada a partir del texto del discurso mediante highchartOutput("nube_palabras"), que permite visualizar los términos más frecuentes del discurso. Este tipo de gráfico ayuda a captar rápidamente los temas y palabras clave sin necesidad de leer el texto completo, proporcionando un contexto rápido y visual del discurso.

# Tarjeta de Visualización del Contenido del Discurso
column(width = 6,
  card(
    card_header("Contenido del Discurso"),
    card_body(
      navset_card_tab(
        tabPanel("Video", div(uiOutput("video_embed"), style = "min-height: 400px;")),
        tabPanel("Nube de Palabras", highchartOutput("nube_palabras", height = "400px"))
      )
    )
  )
)

Tercer panel: salida del modelo

La tercera tarjeta está reservada a mostrar los resultados del análisis realizado por el modelo de lenguaje, en función de la consulta seleccionada o personalizada por el usuario. El uiOutput("respuesta_llm") se encarga de mostrar estos resultados, ya sea un resumen del discurso, los argumentos principales o una interpretación de la postura del diputado en un formato fácilmente legible. El diseño incluye un fondo claro y bordes redondeados, destacando visualmente el análisis y asegurando que el usuario pueda concentrarse en la interpretación de la información. Este espacio, reservado exclusivamente para los resultados, ofrece una experiencia de lectura ordenada y centrada en los datos obtenidos.

# Tarjeta de Resultados del Análisis del Modelo de Lenguaje
column(width = 3,
  card(
    card_header("Resultados del análisis", icon("robot")),
    card_body(
      div(uiOutput("respuesta_llm"), style = "background-color: #f8f9fa; padding: 15px; border-radius: 10px;")
    )
  )
)

Integración

Con estas tres tarjetas, la interfaz de Monitor Legislativo 2 permite que el usuario realice un análisis detallado de discursos de manera organizada y sin complicaciones. Cada panel esta pensado para cumplir una función específica dentro del flujo de trabajo, guiando al usuario de manera intuitiva desde la selección de parámetros hasta la visualización de resultados. Además, el diseño responsivo inmanente a bslib garantiza que la experiencia sea cómoda y funcional en cualquier dispositivo, manteniendo siempre la claridad y accesibilidad de la información.

El servidor: Procesamiento de datos y lógica reactiva

En el servidor de ML 2 gestionamos la lógica que permite que la aplicación responda de manera inmediata a cada interacción del usuario. Esta estructura reactiva permite que los elementos de la interfaz se actualicen según las selecciones, haciendo que el análisis parlamentario sea fluido y accesible. En este apartado revisamos en detalle cómo funcionan cada uno de estos componentes del servidor.

Actualización Dinámica del Menú de Candidatos

El servidor comienza con una función que actualiza el menú de candidatos de acuerdo con el partido seleccionado. Este proceso es crucial para filtrar a los candidatos de manera dinámica, evitando errores y mejorando la experiencia del usuario.

output$candidato_ui <- renderUI({
  req(input$partido)
  candidatos <- unique(discursos_df$diputado[discursos_df$fuerza_politica == input$partido])
  if (length(candidatos) == 0) {
    return(h4("No hay candidatos disponibles para este partido."))
  }
  selectInput("candidato", "Seleccione el Candidato:", choices = candidatos)
})

Usamos renderUI y req(input$partido) para asegurar que este bloque de código solo se ejecute cuando el usuario selecciona un partido. Esto optimiza el servidor, ya que evita cálculos innecesarios cuando no hay un partido elegido. Además, si no hay candidatos disponibles, el código retorna un mensaje de advertencia, manteniendo la interfaz clara y sin errores.

Reactividad para el Discurso Seleccionado

Cada vez que el usuario selecciona un candidato, el servidor reactivo extrae el discurso correspondiente del DataFrame. Este objeto reactivo (eventReactive) es el núcleo para las funciones de análisis y visualización que dependen de la selección del discurso.

discurso_seleccionado <- eventReactive(input$candidato, {
  req(input$candidato)
  discurso <- discursos_df$text[discursos_df$diputado == input$candidato]
  return(discurso)
})

Aquí eventReactive hace que la función solo responda a cambios en la selección del candidato, reduciendo la carga en el sistema, al no recalcular el discurso cuando no es necesario. Este enfoque aumenta la eficiencia del servidor, haciendo que solo los elementos que dependen del discurso se actualicen en tiempo real.

Integración de Videos con vembedr

Cuando el usuario selecciona un candidato, el servidor carga el video asociado, si está disponible. Este proceso se realiza dentro de un observeEvent, que garantiza que la acción solo ocurra cuando hay un cambio en la selección del candidato. Para manejar posibles errores, se utiliza tryCatch, mostrando notificaciones al usuario en caso de que el video no esté disponible.

observeEvent(input$candidato, {
  req(input$candidato)
  video_id <- discursos_df$video_id[discursos_df$diputado == input$candidato]
  
  output$video_embed <- renderUI({
    tryCatch({
      if (length(video_id) == 0 || is.na(video_id) || video_id == "") {
        showNotification("Ups, no tengo un video para mostrar para este candidato.", type = "error")
        return(h4("No se encontró un video para este discurso."))
      }
      embed_url(paste0("https://www.youtube.com/watch?v=", video_id)) %>% 
        use_bs_responsive() %>% 
        use_align("center") %>%
        use_rounded(10)
    }, error = function(e) {
      showNotification("Ups, ocurrió un error al cargar el video.", type = "error")
      return(h4("No se pudo cargar el video para este discurso."))
    })
  })
})

tryCatch actúa como un sistema de seguridad, evitando que errores al cargar el video interrumpan la aplicación. La notificación de error brinda al usuario una explicación clara y evita frustraciones.

Generación de la Nube de Palabras

La función para crear la nube de palabras, que vimos al comienzo, se activa cada vez que el usuario selecciona un candidato. Utilizamos la función renderHighchart para generar el gráfico, lo que permite al usuario visualizar los términos más frecuentes en el discurso en forma de nube de palabras. Esto se realiza en un observeEvent, aprovechando el objeto reactivo discurso_seleccionado.

observeEvent(input$candidato, {
  req(input$candidato)
  discurso <- discurso_seleccionado()
  output$nube_palabras <- renderHighchart({
    generar_nube_palabras(discurso)
  })
})

Análisis del Discurso con el Modelo de Lenguaje

La segunda función, de análisis de discurso es fundamental en la aplicación, y se activa al hacer clic en el botón “Analizar Discurso”. El servidor determina si el usuario optó por una pregunta predefinida o personalizada y genera la respuesta a partir del modelo de lenguaje.

observeEvent(input$analizar, {
  req(input$candidato)
  
  pregunta <- if (!is.null(input$pregunta_personalizada) && input$pregunta_personalizada != "") {
    input$pregunta_personalizada
  } else {
    input$pregunta_predefinida
  }
  
  discurso <- discurso_seleccionado()
  respuesta <- obtener_respuesta_sobre_discurso(discurso, pregunta)
  
  output$respuesta_llm <- renderUI({
    HTML(markdownToHTML(text = respuesta, fragment.only = TRUE))
  })
})

En este bloque, renderUI toma la respuesta del modelo y la presenta en un formato HTML para que el usuario pueda interpretarla de manera directa. La lógica condicional para elegir entre una pregunta predefinida y una personalizada asegura que el usuario tenga flexibilidad en el análisis, sin necesidad de recargar la aplicación.

Funciones Adicionales: Limpiar Pregunta y Mostrar Detalles del Candidato

El servidor también incluye dos funciones adicionales para facilitar el flujo de trabajo: limpiar la pregunta personalizada y mostrar detalles del candidato seleccionado. La función de limpieza reinicia el campo de entrada de texto personalizado, mientras que la segunda función muestra detalles específicos del candidato seleccionado en la interfaz.

observeEvent(input$limpiar, {
  updateTextAreaInput(session, "pregunta_personalizada", value = "")
})

output$detalles_candidato <- renderText({
  req(input$candidato)
  paste("Detalles del candidato", input$candidato, "del partido", input$partido, ".")
})

Ambas funciones adicionales mejoran la experiencia del usuario, ya que permiten un manejo más controlado de las interacciones, sin necesidad de interrumpir el flujo de trabajo.

Reactividad o nada

En Monitor Legislativo 2, la reactividad es fundamental para una experiencia de análisis parlamentario eficiente y sin fricciones. El servidor emplea una lógica reactiva y procesamiento bajo demanda, lo que permite que la interfaz responda de inmediato a cada cambio en lse rieos parámetros de usuario sin cargar innecesariamente el sistema. La importancia de esta estructura radica en que cada ajuste y selección en la aplicación desencadena una actualización instantánea de los elementos visuales y del análisis, permitiendo al usuario navegar rápidamente entre discursos, visualizar resultados y ajustar filtros sin interrupciones en el flujo de trabajo.

La combinación de funciones reactivas, visualizaciones automáticas y un manejo robusto de errores garantiza que el rendimiento de la aplicación esté optimizado. Al procesar solo los datos necesarios en cada interacción la app se adapta eficientemente incluso en equipos más modestos, asegurando que la herramienta se mantenga accesible para una amplia variedad de usuarios. Esta estructura permite que el usuario acceda a nubes de palabras, resúmenes y análisis detallados en tiempo real, transformando el análisis parlamentario en una experiencia intuitiva y sin distracciones técnicas.

La robustez del servidor en el manejo de errores también es crucial. Ante cualquier inconveniente, la aplicación presenta mensajes claros que permiten al usuario retomar su análisis sin contratiempos. Así, nuestro ML II no solo optimiza el proceso de análisis, sino que permite al usuario centrarse en la información relevante al alcance de unos pocos clics, logrando una experiencia de uso precisa, eficiente y enfocada en los datos.

Código completo

El código completo de nuesta app se ve así.

# Cargar librerías necesarias
library(shiny)
library(bslib)
library(dplyr)
library(highcharter)
library(gemini.R)
library(htmltools)
library(markdown)
library(tm)
library(dotenv)
library(bsicons)
library(thematic)
library(vembedr)

# Cargar las variables de entorno del archivo .env
dotenv::load_dot_env()

# Establecer la clave API de gemini usando la variable de entorno
api_key <- Sys.getenv("GEMINI_API_KEY")

# Verificar si se cargó la clave API
if (is.null(api_key) || api_key == "") {
  stop("No se encontró la clave API. Asegúrate de definir GEMINI_API_KEY en el archivo .env.")
}

# Establecer la clave API de gemini
setAPI(api_key)

# Cargar el archivo CSV con los discursos
file_path <- "Sesión Especial 09-10-2024 .csv"
if (file.exists(file_path)) {
  discursos_df <- read.csv(file_path, stringsAsFactors = FALSE)
} else {
  stop("El archivo no se encuentra en el directorio de trabajo.")
}

# Validar que el DataFrame tiene las columnas necesarias
required_columns <- c("fuerza_politica", "diputado", "text", "video_id")
if (!all(required_columns %in% colnames(discursos_df))) {
  stop("El DataFrame 'discursos_df' no contiene las columnas necesarias: fuerza_politica, diputado, text, video_id.")
}


# Habilitar temático para ajustar el estilo de las gráficas a la paleta CSS
thematic::thematic_shiny()

# Función para generar una nube de palabras eliminando stopwords
generar_nube_palabras <- function(texto) {
  stopwords_es <- c(stopwords("es"),"aplausos", "que", "para", "como", "más", "muy", "pero", "también", "por", "entre", "sobre", "nos", "las", "los", "una", "a", "de", "del", "y", "e", "con", "el", "la", "su")
  
  palabras <- strsplit(tolower(texto), "\\W+")[[1]]
  palabras <- palabras[palabras != ""]
  palabras <- palabras[!palabras %in% stopwords_es]
  
  freq_palabras <- as.data.frame(table(palabras))
  colnames(freq_palabras) <- c("word", "n")
  freq_palabras <- freq_palabras[nchar(as.character(freq_palabras$word)) >= 3, ]
  freq_palabras <- freq_palabras[freq_palabras$n > 1, ]
  
  if (nrow(freq_palabras) == 0) {
    return(highchart() %>%
             hc_title(text = "Nube de Palabras - Sin datos suficientes") %>%
             hc_subtitle(text = "No hay suficientes palabras para generar la nube") %>%
             hc_chart(backgroundColor = "#f7f9fc"))
  }
  
  hchart(freq_palabras, "wordcloud", hcaes(name = word, weight = n)) %>%
    hc_title(text = "Nube de Palabras del Discurso") %>%
    hc_tooltip(pointFormat = "<b>{point.name}</b>: {point.weight}") %>%
    hc_chart(backgroundColor = "#f7f9fc")
}

# Función para obtener la respuesta del modelo de Gemini con contexto mejorado
obtener_respuesta_sobre_discurso <- function(discurso, pregunta) {
  prompt <- paste(
    "Eres un experto en discursos políticos. A continuación te proporciono el discurso completo en el parlamento argentino del diputado/a:",
    "\n\n", discurso, 
    "\n\nTu tarea es la siguiente: ", pregunta, 
    "\nPor favor, brinda una respuesta clara basada en el discurso proporcionado."
  )
  
  tryCatch({
    respuesta <- gemini(prompt)
    return(respuesta)
  }, error = function(e) {
    return("Ups! no tengo datos para esa selección, por favor probá con otra.")
  })
}

# Definir la UI
ui <- page_navbar(
  theme = 
    #theme,
    bs_theme(
    bg = "#ffffff", fg = "#343a40", primary = "#98b2c9",
    secondary = "#6c757d",
      base_font = font_google("Poppins"),
      heading_font = font_google("Nunito"),
      code_font = font_google("Source Code Pro"),
      "border-radius" = "10px",
      "input-border-color" = "#ced4da"
  ),
  title = div("ParlamentarIA", 
              style = "font-weight: bold; font-size: 24px; font-family: 'Playfair Display';"),
  
  # Descripción añadida debajo del título con ajuste de estilo
  nav_panel(
    title = "Análisis del Discurso",
    fluidPage(
      fluidRow(
        column(
          width = 12,
          div(
            style = "margin-bottom: 10px; padding: 5px;",  # Reducir espacio
            p(HTML("ParlamentarIA es un <strong>sistema avanzado</strong> de monitoreo asistido por <strong>inteligencia artificial</strong> que permite <strong>analizar la actividad legislativa</strong> en tiempo real. Con acceso a discursos parlamentarios, el sistema proporciona <strong>análisis detallados</strong>, resúmenes automáticos y permite obtener <strong>insights clave</strong> sobre las posturas y argumentos de los legisladores. 
  Los usuarios simplemente seleccionan el <strong>partido</strong>, el <strong>diputado</strong> y el <strong>discurso</strong> para analizarlo en segundos. La herramienta también monitorea debates como el <strong>veto al presupuesto universitario</strong>, realizado el 9 de octubre, donde el oficialismo y sus aliados mantuvieron su postura apoyando el veto presidencial al aumento salarial de los docentes universitarios."),
              style = "text-align: left; font-size: 13px;"
            )
          )
          
        )
      ),
      
      # Mantener el contenido ya existente
      fluidRow(
        column(
          width = 3,
          card(
            card_header("Seleccione los parámetros", icon("filter"), style = "font-family: 'Merriweather'; font-size: 18px;"),
            card_body(
              selectInput("partido", "Seleccione el Partido:", choices = unique(discursos_df$fuerza_politica)),
              uiOutput("candidato_ui"),
              
              # Selector para tipo de análisis
              radioButtons(
                "pregunta_tipo", "Línea de análisis:",
                choices = list("Predefinida" = "predefinida", "Personalizada" = "personalizada"),
                selected = "predefinida"
              ),
              
              # Mostrar selectInput para análisis predefinidos solo cuando está seleccionada esa opción
              conditionalPanel(
                condition = "input.pregunta_tipo == 'predefinida'",
                selectInput("pregunta_predefinida", "Seleccione una línea de análisis predefinida:",
                            choices = list(
                              "Identificar cual es la posición del diputado/a frente al tema planteado"="Identificar cual es la posición del diputado/a frente al tema planteado",
                              "Efectuar un resumen de la exposición" = "Efectuar un resumen de la exposición",
                              "Destacar los 3 principales argumentos de la exposición" = "Destacar los 3 principales argumentos de la exposición",
                              "Mencionar los elementos propositivos del discurso" = "Mencionar los elementos propositivos del discurso",
                              "Identificar el mayor problema expuesto y los posibles culpables" = "Identificar el mayor problema expuesto y los posibles culpables"
                            ))
              ),
              
              # Mostrar textAreaInput solo si se selecciona la opción personalizada
              conditionalPanel(
                condition = "input.pregunta_tipo == 'personalizada'",
                textAreaInput("pregunta_personalizada", "Escribí tu propia orden de análisis:", "", height = "100px")
              ),
              
              actionButton("analizar", "Analizar Discurso", 
                           class = "btn btn-primary btn-lg btn-block mt-3",
                           style = "font-size: 14px; padding: 10px;"),
              actionButton("limpiar", "Limpiar Pregunta", 
                           class = "btn btn-secondary btn-lg btn-block mt-2", 
                           style = "font-size: 14px; padding: 10px;")
            )
          )
        ),
        
        column(
          width = 6,
          card(
            max_height = 450,
            full_screen = TRUE,
            card_header(
              "Contenido del Discurso",
              tooltip(icon("info-circle"), "Elija entre ver el video o la nube de palabras."),
              style = "font-family: 'Merriweather'; font-size: 18px;"
            ),
            card_body(
              navset_card_tab(
                tabPanel("Video", div(uiOutput("video_embed"), style = "min-height: 400px;")),
                tabPanel("Nube de Palabras", highchartOutput("nube_palabras", height = "400px"))
              )
            )
          )
        ),
        
        column(
          width = 3,
          card(
            full_screen = TRUE,
            card_header("Resultados del análisis", 
                        class = "bg-secondary",
                        icon("robot"),
                        style = "font-family: 'Playfair Display'; font-size: 20px;"),
            card_body(
              div(
                style = "background-color: #f8f9fa; padding: 15px; border: 1px solid #DDD; border-radius: 10px;",
                h5(" ", style = "font-family: 'Merriweather';"),
                uiOutput("respuesta_llm")
              )
            )
          )
        )
      )
    )
  ),
  # Panel de "Acerca de la Herramienta" con mejoras de estilo
  nav_panel(
    title = "Acerca de la Herramienta",
    fluidPage(
      # Instrucciones en forma de tarjetas con disposición 2x2
      fluidRow(
        column(
          width = 6,  # 6 de 12 para tener dos columnas por fila
          card(
            card_header("Funcionamiento", icon("cogs"),
                        #class = "bg-primary text-white"
                        ),
            card_body(
              p("Esta herramienta permite analizar discursos parlamentarios de manera avanzada, 
                utilizando videos oficiales publicados por la Cámara de Diputados de Argentina. 
                Ofrece la capacidad de seleccionar cualquier discurso, procesarlo con técnicas 
                de inteligencia artificial, y generar análisis detallados, resúmenes automáticos 
                y visualización interactiva de datos clave.", style = "text-align: justify;")
            ),
            style = "box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); border-radius: 10px;"
          )
        ),
        column(
          width = 6,
          card(
            card_header("Contexto", icon("signal"),
                        #class = "bg-info text-white"
                        ),
            card_body(
              p("La herramienta monitorea el debate parlamentario sobre el veto al 
                presupuesto universitario, realizado el 9 de octubre. En dicho debate, 
                el oficialismo y sus aliados lograron mantener su postura, 
                apoyando el veto presidencial que rechazó el aumento salarial a
                los docentes universitarios en todo el país.", style = "text-align: justify;")
            ),
            style = "box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); border-radius: 10px;"
          )
        )
      ),
      fluidRow(
        column(
          width = 6,
          card(
            card_header("Modelo utilizado", icon("pencil-alt"),
                       # class = "bg-success text-white"
                       ),
            card_body(
              p("El análisis de los discursos se basa en el modelo avanzado Gemini 
                de Google, diseñado para comprender el contexto político de manera 
                profunda. Este modelo permite interpretar con precisión las 
                intervenciones parlamentarias y generar respuestas claras y 
                específicas a preguntas complejas sobre las posturas y argumentos 
                expuestos.", style = "text-align: justify;")
            ),
            style = "box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); border-radius: 10px;"
          )
        ),
        column(
          width = 6,
          card(
            card_header("Privacidad y Seguridad", icon("shield-alt"),
                        #class = "bg-warning text-dark"
                        ),
            card_body(
              p("La herramienta garantiza la privacidad de los 
                usuarios al no almacenar datos personales ni conservar información 
                de los análisis fuera del entorno seguro en el que se ejecuta. 
                Todo el procesamiento de los discursos es temporal y se realiza 
                de forma local, asegurando la confidencialidad total de los datos.", 
                style = "text-align: justify;")
            ),
            style = "box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); border-radius: 10px;"
          )
        )
      ),
      fluidRow(
        column(
          width = 12,
          card(
            card_header("Créditos y código", icon("user-circle"),
                        class = "bg-primary text-black"),
            p(HTML("Esta herramienta fue desarrollada por <strong>Pedro Orden</strong>, 
                     sociólogo y científico de datos. Puedes encontrar más información sobre su 
                     trabajo en su <a href='https://linktr.ee/pedroorden' target='_blank'>Linktree</a>. 
                     El código de la aplicación está disponible de forma abierta en 
                     <a href='https://github.com/pedroorden/parlamentaria' target='_blank'>GitHub</a>, 
                     y puede ser reutilizado bajo la licencia 
                     <a href='https://creativecommons.org/licenses/by-nc/4.0/' target='_blank'>Creative Commons Attribution-NonCommercial 4.0</a>. 
                     Si reutilizas el código, por favor atribúyeme como creador, pero no se permite su uso con fines comerciales."), 
              style = "text-align: justify;"),
            style = "box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.1); border-radius: 10px;"
          )
        )
      )
    )
  )
)

# Definir el servidor de la aplicación
server <- function(input, output, session) {
  
  # Actualizar menú de candidatos
  output$candidato_ui <- renderUI({
    req(input$partido)
    candidatos <- unique(discursos_df$diputado[discursos_df$fuerza_politica == input$partido])
    if (length(candidatos) == 0) {
      return(h4("No hay candidatos disponibles para este partido."))
    }
    selectInput("candidato", "Seleccione el Candidato:", choices = candidatos)
  })
  
  # Reactivo para el discurso seleccionado
  discurso_seleccionado <- eventReactive(input$candidato, {
    req(input$candidato)
    discurso <- discursos_df$text[discursos_df$diputado == input$candidato]
    return(discurso)
  })
  
  # Cargar el video automáticamente con vembedr
  observeEvent(input$candidato, {
    req(input$candidato)
    video_id <- discursos_df$video_id[discursos_df$diputado == input$candidato]
    
    output$video_embed <- renderUI({
      tryCatch({
        if (length(video_id) == 0 || is.na(video_id) || video_id == "") {
          showNotification("Ups, no tengo un video para mostrar para este candidato.", type = "error")
          return(h4("No se encontró un video para este discurso."))
        }
        embed_url(paste0("https://www.youtube.com/watch?v=", video_id)) %>% 
          use_bs_responsive() %>% 
          use_align("center") %>%
          use_rounded(10)
      }, error = function(e) {
        showNotification("Ups, ocurrió un error al cargar el video.", type = "error")
        return(h4("No se pudo cargar el video para este discurso."))
      })
    })
  })
  
  
  # Generar la nube de palabras
  observeEvent(input$candidato, {
    req(input$candidato)
    discurso <- discurso_seleccionado()
    output$nube_palabras <- renderHighchart({
      generar_nube_palabras(discurso)
    })
  })
  
  # Analizar el discurso
  observeEvent(input$analizar, {
    req(input$candidato)
    
    # Obtener la orden de análisis, ya sea predefinida o personalizada
    pregunta <- if (!is.null(input$pregunta_personalizada) && input$pregunta_personalizada != "") {
      input$pregunta_personalizada
    } else {
      input$pregunta_predefinida
    }
    
    discurso <- discurso_seleccionado()
    respuesta <- obtener_respuesta_sobre_discurso(discurso, pregunta)
    
    output$respuesta_llm <- renderUI({
      HTML(markdownToHTML(text = respuesta, fragment.only = TRUE))
    })
  })
  
  # Limpiar pregunta
  observeEvent(input$limpiar, {
    updateTextAreaInput(session, "pregunta_personalizada", value = "")
  })
  
  # Mostrar detalles del candidato
  output$detalles_candidato <- renderText({
    req(input$candidato)
    paste("Detalles del candidato", input$candidato, "del partido", input$partido, ".")
  })
}

# Ejecutar la aplicación
shinyApp(ui = ui, server = server)

Balance

A lo largo de este artículo, hemos explorado la creación y capacidades del Monitor Legislativo 2, una plataforma que ejemplifica cómo las herramientas personalizadas pueden abordar necesidades específicas del análisis legislativo y, al mismo tiempo, abrir nuevas posibilidades en la investigación social. A medida que aumenta la cantidad y complejidad de los datos con los que trabajamos, contar con herramientas diseñadas para analizar fenómenos sociales se vuelve esencial. La app que acabamos de armar permite superar el procesamiento básico de datos, ofreciendo una interpretación contextual y matizada, ideal para entornos como el legislativo, donde los discursos reflejan posturas políticas, matices ideológicos y dinámicas de poder.

La experiencia del monitor no solo muestra el poder de las herramientas digitales en la investigación social, sino que también da cuenta de la importancia de diseñarlas con un enfoque adaptado. Al permitir un análisis detallado en tiempo real de grandes volúmenes de datos textuales, la herramienta aporta una flexibilidad crítica que puede extenderse más allá del ámbito legislativo a campos como estudios de opinión pública, análisis de medios y otros espacios donde el lenguaje y las interacciones son clave. La capacidad de captar estos elementos contextuales ofrece una comprensión más rica de las dinámicas sociales y políticas en juego, acercando al investigador a una visión completa del fenómeno en estudio.

Sin embargo, es importante señalar que Monitor Legislativo 2 y otras herramientas similares no buscan reemplazar el papel del investigador social, sino potenciarlo. La herramienta facilita la sistematización y el procesamiento de grandes cantidades de datos, pero el análisis crítico y la interpretación siguen dependiendo de la experiencia y el criterio del científico. Los investigadores aportan una comprensión profunda del contexto, las variables y las relaciones que no pueden ser replicadas automáticamente. Al combinar las capacidades de procesamiento de la herramienta con el conocimiento y la intuición del investigador, se logra una sinergia que permite abordar temas complejos y emergentes con un conocimiento de primera mano.

La arquitectura de código abierto y el diseño accesible del Monitor Legislativo 2 refuerzan su papel como recurso colaborativo y transparente en la investigación social. Al abrir el código, no solo se garantiza la replicabilidad y verificabilidad de los análisis, sino que también se permite que la comunidad investigadora lo adapte y mejore según sus necesidades. Este enfoque colaborativo permite que otros investigadores y analistas contribuyan activamente a la evolución de la herramienta, ampliando su aplicabilidad y fortaleciendo su valor como herramienta común para la exploración y el análisis de fenómenos sociales.

Además, el diseño optimizado y la adaptabilidad de la herramienta permiten que sea accesible a una amplia variedad de usuarios, independientemente de los recursos técnicos disponibles. Esta democratización del acceso a herramientas avanzadas de análisis posibilita que más investigadores y profesionales en ciencias sociales participen en el análisis de datos complejos, fomentando un conocimiento más inclusivo y diverso. La herramienta, al ser accesible en distintas plataformas y contextos, apoya el trabajo de investigadores de todo tipo, facilitando que el análisis social sea cada vez más accesible y completo.

El aggiornamiento del ML II ilustra el potencial transformador de los modelos de lenguaje en el campo del análisis y el conocimiento social. Si bien el análisis legislativo ha sido un caso práctico para aplicar estas herramientas, el verdadero valor de este avance radica en su capacidad de escalar hacia otros ámbitos del análisis social, desde la opinión pública hasta los medios de comunicación. La incorporación de modelos de lenguaje avanzados nos va a permitir cada vez mas una comprensión profunda de los contextos y matices que antes eran difíciles de captar de forma automatizada.

Con miras al futuro, el objetivo es seguir perfeccionando estas herramientas, haciendo posible no solo un análisis más detallado y accesible, sino también una comprensión más transparente de los fenómenos sociales en toda su complejidad, acercando a profesionales, investigadores y la ciudadanía a una visión más rica y matizada de la realidad que nos rodea.

ps:

La idea es comenzar a compartir estos desarrollos experimentales en el nuevo newsletter del NIS, así que invito muy activamente a que se suscriban a la gacetilla del sitio.

Notas

  1. Aquí contamos cómo se genera la api de Gemini.↩︎

Reutilización

Cómo citar

BibTeX
@online{damián_orden2024,
  author = {Damián Orden, Pedro},
  title = {Monitor Legislativo 2},
  date = {2024-10-27},
  url = {https://tecysoc.netlify.app/posts/monitor legislativo 2/},
  langid = {es-Es}
}
Por favor, cita este trabajo como:
Damián Orden, Pedro. 2024. “Monitor Legislativo 2.” October 27, 2024. https://tecysoc.netlify.app/posts/monitor legislativo 2/.